/******************************************************************************* * Copyright (c) 2009 the CHISEL group and contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Del Myers -- initial API and implementation *******************************************************************************/ package org.eclipse.zest.custom.sequence.figures.internal; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import org.eclipse.draw2d.Border; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.LayoutManager; import org.eclipse.draw2d.TreeSearch; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; /** * The base implementation for graphical figures. */ public class QuickClearingFigure extends Figure { private static final Rectangle PRIVATE_RECT = new Rectangle(); private static final Point PRIVATE_POINT = new Point(); private static final int FLAG_VISIBLE = new Integer(1 << 2).intValue(), FLAG_ENABLED = new Integer(1 << 4).intValue(), FLAG_FOCUS_TRAVERSABLE = new Integer(1 << 5).intValue(); static final int FLAG_REALIZED = 1 << 31; /** * The largest flag defined in this class. If subclasses define flags, they should * declare them as larger than this value and redefine MAX_FLAG to be their largest flag * value. * <P> * This constant is evaluated at runtime and will not be inlined by the compiler. */ protected static int MAX_FLAG = FLAG_FOCUS_TRAVERSABLE; /** * The rectangular area that this Figure occupies. */ protected Rectangle bounds = new Rectangle(0, 0, 0, 0); private LayoutManager layoutManager; /** * The flags for this Figure. */ protected int flags = FLAG_VISIBLE | FLAG_ENABLED; private List<IFigure> children = new LinkedList<IFigure>();; /** * @see IFigure#add(IFigure, Object, int) */ public void add(IFigure figure, Object constraint, int index) { if (index < -1 || index > children.size()) throw new IndexOutOfBoundsException("Index does not exist"); //$NON-NLS-1$ //Check for Cycle in hierarchy for (IFigure f = this; f != null; f = f.getParent()) if (figure == f) throw new IllegalArgumentException( "Figure being added introduces cycle"); //$NON-NLS-1$ //Detach the child from previous parent if (figure.getParent() != null) figure.getParent().remove(figure); if (index == -1) children.add(figure); else children.add(index, figure); figure.setParent(this); if (layoutManager != null) layoutManager.setConstraint(figure, constraint); revalidate(); if (getFlag(FLAG_REALIZED)) figure.addNotify(); figure.repaint(); } /** * Called after the receiver's parent has been set and it has been added to its parent. * * @since 2.0 */ public void addNotify() { if (getFlag(FLAG_REALIZED)) throw new RuntimeException("addNotify() should not be called multiple times"); //$NON-NLS-1$ setFlag(FLAG_REALIZED, true); for (Iterator<IFigure> i = (Iterator<IFigure>)children.iterator();i.hasNext();) { IFigure child = i.next(); child.addNotify(); } } /** * Returns a descendant of this Figure such that the Figure returned contains the point * (x, y), and is accepted by the given TreeSearch. Returns <code>null</code> if none * found. * @param x The X coordinate * @param y The Y coordinate * @param search the TreeSearch * @return The descendant Figure at (x,y) */ protected IFigure findDescendantAtExcluding(int x, int y, TreeSearch search) { PRIVATE_POINT.setLocation(x, y); translateFromParent(PRIVATE_POINT); if (!getClientArea(Rectangle.SINGLETON).contains(PRIVATE_POINT)) return null; x = PRIVATE_POINT.x; y = PRIVATE_POINT.y; IFigure fig; ListIterator<IFigure> i = children.listIterator(children.size()); while (i.hasPrevious()) { fig = i.previous(); if (fig.isVisible()) { fig = fig.findFigureAt(x, y, search); if (fig != null) return fig; } } //No descendants were found return null; } /** * Searches this Figure's children for the deepest descendant for which * {@link #isMouseEventTarget()} returns <code>true</code> and returns that descendant or * <code>null</code> if none found. * @see #findMouseEventTargetAt(int, int) * @param x The X coordinate * @param y The Y coordinate * @return The deepest descendant for which isMouseEventTarget() returns true */ protected IFigure findMouseEventTargetInDescendantsAt(int x, int y) { PRIVATE_POINT.setLocation(x, y); translateFromParent(PRIVATE_POINT); if (!getClientArea(Rectangle.SINGLETON).contains(PRIVATE_POINT)) return null; IFigure fig; for (int i = children.size(); i > 0;) { i--; fig = (IFigure)children.get(i); if (fig.isVisible() && fig.isEnabled()) { if (fig.containsPoint(PRIVATE_POINT.x, PRIVATE_POINT.y)) { fig = fig.findMouseEventTargetAt(PRIVATE_POINT.x, PRIVATE_POINT.y); return fig; } } } return null; } /** * @see IFigure#getChildren() */ @SuppressWarnings("unchecked") public List getChildren() { return children; } /** * @see IFigure#invalidateTree() */ public void invalidateTree() { invalidate(); for (Iterator<IFigure> iter = children.iterator(); iter.hasNext();) { IFigure child = (IFigure) iter.next(); child.invalidateTree(); } } /** * Paints this Figure's children. The caller must save the state of the graphics prior to * calling this method, such that <code>graphics.restoreState()</code> may be called * safely, and doing so will return the graphics to its original state when the method was * entered. * <P> * This method must leave the Graphics in its original state upon return. * @param graphics the graphics used to paint * @since 2.0 */ protected void paintChildren(Graphics graphics) { IFigure child; Rectangle clip = Rectangle.SINGLETON; for (Iterator<IFigure> i = children.iterator(); i.hasNext();) { child = i.next(); if (child.isVisible() && child.intersects(graphics.getClip(clip))) { graphics.clipRect(child.getBounds()); child.paint(graphics); graphics.restoreState(); } } } /** * Paints this Figure's client area. The client area is typically defined as the anything * inside the Figure's {@link Border} or {@link Insets}, and by default includes the * children of this Figure. On return, this method must leave the given Graphics in its * initial state. * @param graphics The Graphics used to paint * @since 2.0 */ protected void paintClientArea(Graphics graphics) { if (children.isEmpty()) return; boolean optimizeClip = getBorder() == null || getBorder().isOpaque(); if (useLocalCoordinates()) { graphics.translate( getBounds().x + getInsets().left, getBounds().y + getInsets().top); if (!optimizeClip) graphics.clipRect(getClientArea(PRIVATE_RECT)); graphics.pushState(); paintChildren(graphics); graphics.popState(); graphics.restoreState(); } else { if (optimizeClip) paintChildren(graphics); else { graphics.clipRect(getClientArea(PRIVATE_RECT)); graphics.pushState(); paintChildren(graphics); graphics.popState(); graphics.restoreState(); } } } /** * Translates this Figure's bounds, without firing a move. * @param dx The amount to translate horizontally * @param dy The amount to translate vertically * @see #translate(int, int) * @since 2.0 */ protected void primTranslate(int dx, int dy) { bounds.x += dx; bounds.y += dy; if (useLocalCoordinates()) { fireCoordinateSystemChanged(); return; } for (Iterator<IFigure> i = children.iterator(); i.hasNext();) i.next().translate(dx, dy); } /** * Removes the given child Figure from this Figure's hierarchy and revalidates this * Figure. The child Figure's {@link #removeNotify()} method is also called. * @param figure The Figure to remove */ public void remove(IFigure figure) { if ((figure.getParent() != this)) throw new IllegalArgumentException( "Figure is not a child"); //$NON-NLS-1$ if (getFlag(FLAG_REALIZED)) figure.removeNotify(); if (layoutManager != null) layoutManager.remove(figure); // The updates in the UpdateManager *have* to be // done asynchronously, else will result in // incorrect dirty region corrections. figure.erase(); figure.setParent(null); children.remove(figure); revalidate(); } /** * Removes all figures in this figure. */ public void removeAll() { LinkedList<IFigure> oldChildren = (LinkedList<IFigure>)children; this.children = new LinkedList<IFigure>(); ListIterator<IFigure> i = oldChildren.listIterator(children.size()); while (i.hasPrevious()) { IFigure next = i.previous(); next.erase(); next.setParent(null); if (getFlag(FLAG_REALIZED)) next.removeNotify(); layoutManager.remove(next); } revalidate(); } /** * Called prior to this figure's removal from its parent */ public void removeNotify() { for (Iterator<IFigure> i = children.iterator(); i.hasNext();) i.next().removeNotify(); if (internalGetEventDispatcher() != null) internalGetEventDispatcher().requestRemoveFocus(this); setFlag(FLAG_REALIZED, false); } }